Skip to content

Optimization for concurrent Key Vault Ref resolution - deduplicate requests to the same secret#328

Open
linglingye001 wants to merge 4 commits into
mainfrom
linglingye/dedup-key-vault-ref
Open

Optimization for concurrent Key Vault Ref resolution - deduplicate requests to the same secret#328
linglingye001 wants to merge 4 commits into
mainfrom
linglingye/dedup-key-vault-ref

Conversation

@linglingye001

Copy link
Copy Markdown
Member

No description provided.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds in-flight request deduplication to Key Vault secret resolution so that concurrent configuration settings referencing the same secret URI don’t trigger redundant Key Vault (or resolver) calls.

Changes:

  • Added an in-flight promise map in AzureKeyVaultSecretProvider.getSecretValue() to deduplicate concurrent secret fetches by secret identifier.
  • Added test coverage validating deduplication in parallel and sequential modes, secret-version independence, failure behavior, and refresh behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/keyvault/keyVaultSecretProvider.ts Introduces in-flight request deduplication for identical secret identifiers and ensures in-flight entries are cleared after completion.
test/keyvault.test.ts Adds a new test suite covering request deduplication behavior across parallel/sequential resolution, versions, failures, and refresh rounds.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread test/keyvault.test.ts
Comment on lines +295 to +296
await sleepInMs(60_000 + 100);
await settings.refresh();

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

Comment thread src/appConfigurationImpl.ts Outdated
Comment on lines 921 to 926
// Resolve every reference. In parallel mode the values are served from the warmed cache
// without any additional I/O.
for (const setting of secretReferences) {
const [key, value] = await this.#processKeyValue(setting);
resultHandler(key, value);
}
Comment thread src/appConfigurationImpl.ts Outdated
Comment on lines +909 to +916
if (secretId === undefined) {
// The reference cannot be parsed; resolve it individually so the appropriate error
// is surfaced when it is processed below.
uniqueSecretReferences.push(setting);
} else if (!seenSecretIds.has(secretId)) {
seenSecretIds.add(secretId);
uniqueSecretReferences.push(setting);
}
Comment thread src/keyvault/keyVaultSecretProvider.ts Outdated
Comment on lines +37 to +38
// Return the cached value if available. The cache is invalidated externally (on secret refresh
// or when a key-value change is detected) so a stale value is never served.
Comment thread test/keyvault.test.ts
Comment on lines +294 to +296
// After the secret refresh interval elapses, the refresh round re-fetches once.
await sleepInMs(60_000 + 100);
await settings.refresh();
Comment thread src/keyvault/keyVaultKeyValueAdapter.ts Outdated
Comment thread src/appConfigurationImpl.ts Outdated
}

// Invalidate cached secret values so the refresh round re-fetches them from Key Vault.
this.#keyVaultAdapter.clearCache();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the introduction of features such as secret refresh, parallel loading, and deduplication, AppConfigurationImpl now needs more fine-grained control over secret loading and caching.

When KeyValueAdapter.onChangeDetected is available, but we bypass it and directly call SecretProvider or Adapter.clearCache, I do not think the existing KeyVaultKeyValueAdapter abstraction remains a sound design.

@zhiyuanliang-ms zhiyuanliang-ms Jul 13, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can introduce preload(settings: ConfigurationSetting[]): Promise<void>; to IKeyVaultAdapter

This method will dedup and warm the secret cache.

SecretProvider needs to be updated. getSecretValue should now only serve secret value from cache (if no cache is found, fallback to send request), the timer gate should be removed.

The timer gate should apply for the preload path only.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed offline

Comment thread src/keyValueAdapter.ts
* Optionally batch-resolves settings ahead of processKeyValue, e.g. to deduplicate and warm up
* Key Vault secret requests so that processKeyValue only reads from cache.
*/
preload?(settings: ConfigurationSetting[]): Promise<void>;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method should not be optional. We should make it and its comment consistent with OnChangeDetected.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the comment we should explain the parameter: whether it should be just a bunch of configuration settings or a set of certain type of configuration settings that the adapter can process

@zhiyuanliang-ms zhiyuanliang-ms Jul 14, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually prefer to let preload only take settings that the adapter can process. This will match the orchestrator(appConfigurationImpl) + kv adapter design.

The orchestrator should call preload like this:

for (const adapter of this.#adapters) {
    await adapter.preload.(settings.filter(s => adapter.canProcess(s)));
}

The above code has very clear intention.
But the concern here is that it will increase the cost to MxN (M is adapater count, N is settings count)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With that concern, I think maybe we should make preload and onChangeDetected optional method under the IKeyValueAdapter to leverage the JS/TS language characteristics for better performance.

*/
async preload(settings: ConfigurationSetting[]): Promise<void> {
if (!this.#keyVaultOptions) {
return; // nothing to do; processKeyValue will throw the proper ArgumentError

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return; // nothing to do; processKeyValue will throw the proper ArgumentError
return; // no-op when keyVaultOptions is not configured

* timer and the parallel resolution option. This is best-effort: per-secret failures are swallowed so
* that the error is surfaced with full context later by getSecretValue during resolution.
*/
async preloadSecrets(secretIdentifiers: KeyVaultSecretIdentifier[]): Promise<void> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

preload is the concept from the abstraction of kv adapter. This business logic should be placed in adapter implementation.

I think secret provider should offer two methods: 1. loadSecretValue 2. getSecretValue

loadSecretValue has refresh timer gate, if timer is not due, serve secret value from cache, when there is no cache or timer is due, the fallback is to send request to key vault to get secret value
getSecretValue simply serve secret value from cache, when there is no cache, the fallback is loadSecretValue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants